package org.lex.game.sudoku.util;

import java.awt.EventQueue;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicReference;

import javax.swing.SwingUtilities;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SyncProxy {
	private static final Logger log = LoggerFactory.getLogger(SyncProxy.class);

	public static Object newSyncInstance(Object delegate, SyncFilter syncFilter, SyncExcutor syncExecutor,
			Class<?>... interfaces) {
		return Proxy.newProxyInstance(delegate.getClass().getClassLoader(), interfaces, new SyncInvocationHandler(
				delegate, syncFilter, syncExecutor));
	}

	public static Object newSwingSyncInstance(Object delegate, SyncFilter syncFilter, Class<?>... interfaces) {
		return Proxy.newProxyInstance(delegate.getClass().getClassLoader(), interfaces, new SyncInvocationHandler(
				delegate, syncFilter, new SwingSyncExecutor()));
	}

	public static interface SyncFilter {
		public boolean needSync(Object delegate, Method method, Object[] args);
	}

	public static interface SyncExcutor {
		public Object execute(Callable<?> call) throws Exception;
	}

	public static class SwingSyncExecutor implements SyncExcutor {

		@Override
		public Object execute(final Callable<?> call) throws Exception {
			if (EventQueue.isDispatchThread()) {
				return call.call();
			} else {
				final AtomicReference<Object> ref = new AtomicReference<Object>(null);
				SwingUtilities.invokeAndWait(new Runnable() {
					@Override
					public void run() {
						Object result = null;
						try {
							result = call.call();
						} catch (Exception e) {
							e.printStackTrace();
						}
						ref.set(result);
					}
				});
				return ref.get();
			}
		}

	}

	private static final class SyncInvocationHandler implements InvocationHandler {
		private final Object delegate;
		private final SyncFilter syncFilter;
		private final SyncExcutor syncExecutor;
		private MethodInvoker invoker = new MethodInvoker();

		public SyncInvocationHandler(Object delegate, SyncFilter syncFilter, SyncExcutor syncExecutor) {
			this.delegate = delegate;
			this.syncFilter = syncFilter;
			this.syncExecutor = syncExecutor;
		}

		@Override
		public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable {
			Object result = null;
			boolean shouldSync = this.syncFilter.needSync(delegate, method, args);
			if (shouldSync) {
				log.info("sync " + method);
				result = this.syncExecutor.execute(new Callable<Object>() {
					@Override
					public Object call() throws Exception {
						return invoker.invoke(delegate, method, args);
					}
				});
			} else {
				result = invoker.invoke(delegate, method, args);
			}
			return result;
		}
	}

	private static final class MethodInvoker {
		public Object invoke(Object delegate, Method method, Object[] args) throws NoSuchMethodException,
				SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
			Class<?>[] argsClasses = null;
			if (null != args) {
				argsClasses = new Class<?>[args.length];
				for (int i = 0; i < args.length; i++) {
					argsClasses[i] = SyncProxy.getClass(args[i]);
				}
			}
			Method m = delegate.getClass().getMethod(method.getName(), argsClasses);
			return m.invoke(delegate, args);
		}
	}

	private static Class<?> getClass(Object o) {
		if (null == o) {
			return null;
		}
		Class<?> clz = o.getClass();
		if (clz == Boolean.class) {
			return boolean.class;
		} else if (clz == Character.class) {
			return char.class;
		} else if (clz == Byte.class) {
			return byte.class;
		} else if (clz == Short.class) {
			return short.class;
		} else if (clz == Integer.class) {
			return int.class;
		} else if (clz == Long.class) {
			return long.class;
		} else if (clz == Float.class) {
			return float.class;
		} else if (clz == Double.class) {
			return double.class;
		} else if (clz == Void.class) {
			return void.class;
		} else {
			return clz;
		}

	}
}
